﻿// Decompiled with JetBrains decompiler
// Type: Bannerlord.BUTR.Shared.Helpers.ModuleInfoHelper
// Assembly: MCMv5, Version=5.10.2.0, Culture=neutral, PublicKeyToken=null
// MVID: 7AB326B9-B2B4-41C2-9177-68E944071101
// Assembly location: D:\steam\steamapps\workshop\content\261550\2859238197\bin\Win64_Shipping_Client\MCMv5.dll

using Bannerlord.BUTR.Shared.Extensions;
using Bannerlord.ModuleManager;
using HarmonyLib;
using HarmonyLib.BUTR.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml;
using TaleWorlds.Engine;
using TaleWorlds.Library;
using TaleWorlds.Localization;
using TaleWorlds.ModuleManager;
using TaleWorlds.MountAndBlade;

#nullable enable
namespace Bannerlord.BUTR.Shared.Helpers
{
  [ExcludeFromCodeCoverage]
  [DebuggerNonUserCode]
  internal static class ModuleInfoHelper
  {
    public const string ModulesFolder = "Modules";
    public const string SubModuleFile = "SubModule.xml";
    private static readonly AccessTools.FieldRef<IPlatformModuleExtension>? _platformModuleExtensionField;
    private static Lazy<List<ModuleInfoExtendedWithMetadata>> _cachedModules = new Lazy<List<ModuleInfoExtendedWithMetadata>>((Func<List<ModuleInfoExtendedWithMetadata>>) (() =>
    {
      List<ModuleInfoExtendedWithMetadata> extendedWithMetadataList = new List<ModuleInfoExtendedWithMetadata>();
      HashSet<string> stringSet = new HashSet<string>();
      foreach (ModuleInfoExtendedWithMetadata extendedWithMetadata in ModuleInfoHelper.GetPhysicalModules().Concat<ModuleInfoExtendedWithMetadata>(ModuleInfoHelper.GetPlatformModules()))
      {
        if (!stringSet.Contains(extendedWithMetadata.Id.ToLower()))
        {
          stringSet.Add(extendedWithMetadata.Id.ToLower());
          extendedWithMetadataList.Add(extendedWithMetadata);
        }
      }
      return extendedWithMetadataList;
    }), LazyThreadSafetyMode.ExecutionAndPublication);
    private static ConcurrentDictionary<string, string> _cachedAssemblyLocationToModulePath = new ConcurrentDictionary<string, string>();

    static ModuleInfoHelper()
    {
      ModuleInfoHelper._platformModuleExtensionField = AccessTools2.StaticFieldRefAccess<IPlatformModuleExtension>("TaleWorlds.ModuleManager.ModuleHelper:_platformModuleExtension");
    }

    public static ModuleInfoExtendedWithMetadata? LoadFromId(string id)
    {
      return ModuleInfoHelper.GetModules().FirstOrDefault<ModuleInfoExtendedWithMetadata>((Func<ModuleInfoExtendedWithMetadata, bool>) (x => x.Id == id));
    }

    public static IEnumerable<ModuleInfoExtendedWithMetadata> GetLoadedModules()
    {
      string[] moduleNames = Utilities.GetModulesNames();
      if (moduleNames.Length != 0)
      {
        Dictionary<string, ModuleInfoExtendedWithMetadata> allModulesAvailable = ModuleInfoHelper.GetModules().ToDictionary<ModuleInfoExtendedWithMetadata, string, ModuleInfoExtendedWithMetadata>((Func<ModuleInfoExtendedWithMetadata, string>) (x => x.Id), (Func<ModuleInfoExtendedWithMetadata, ModuleInfoExtendedWithMetadata>) (x => x));
        string[] strArray = moduleNames;
        for (int index = 0; index < strArray.Length; ++index)
        {
          string modulesId = strArray[index];
          ModuleInfoExtendedWithMetadata moduleInfo;
          if (allModulesAvailable.TryGetValue(modulesId, out moduleInfo))
            yield return moduleInfo;
          moduleInfo = (ModuleInfoExtendedWithMetadata) null;
          modulesId = (string) null;
        }
        strArray = (string[]) null;
      }
    }

    public static IEnumerable<ModuleInfoExtendedWithMetadata> GetModules()
    {
      return (IEnumerable<ModuleInfoExtendedWithMetadata>) ModuleInfoHelper._cachedModules.Value;
    }

    public static string? GetModulePath(System.Type? type)
    {
      if ((object) type == null || string.IsNullOrWhiteSpace(type.Assembly.Location))
        return (string) null;
      string fullName;
      return ModuleInfoHelper._cachedAssemblyLocationToModulePath.TryGetValue(type.Assembly.Location, out fullName) ? fullName : (fullName = GetMainDirectory(new FileInfo(System.IO.Path.GetFullPath(type.Assembly.Location)).Directory)?.FullName);

      static DirectoryInfo? GetMainDirectory(DirectoryInfo? directoryInfo)
      {
        for (; directoryInfo?.Parent != null && directoryInfo.Exists; directoryInfo = directoryInfo.Parent)
        {
          if (directoryInfo.GetFiles("SubModule.xml").Length == 1)
            return directoryInfo;
        }
        return (DirectoryInfo) null;
      }
    }

    public static string? GetModulePath(ModuleInfoExtended module)
    {
      return ModuleInfoHelper._cachedModules.Value.FirstOrDefault<ModuleInfoExtendedWithMetadata>((Func<ModuleInfoExtendedWithMetadata, bool>) (x => x.Id == module.Id))?.Path;
    }

    public static ModuleInfoExtendedWithMetadata? GetModuleByType(System.Type? type)
    {
      string modulePath = ModuleInfoHelper.GetModulePath(type);
      return ModuleInfoHelper._cachedModules.Value.FirstOrDefault<ModuleInfoExtendedWithMetadata>((Func<ModuleInfoExtendedWithMetadata, bool>) (x => x.Path == modulePath));
    }

    private static string GetFullPathWithEndingSlashes(string input)
    {
      return string.Format("{0}{1}", (object) System.IO.Path.GetFullPath(input).TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar), (object) System.IO.Path.DirectorySeparatorChar);
    }

    public static IEnumerable<ModuleInfoExtendedWithMetadata> GetPhysicalModules()
    {
      if (!string.IsNullOrEmpty(BasePath.Name))
      {
        string[] strArray = Directory.GetDirectories(System.IO.Path.Combine(BasePath.Name, "Modules"));
        for (int index = 0; index < strArray.Length; ++index)
        {
          string modulePath = strArray[index];
          XmlDocument xml;
          ModuleInfoExtended moduleInfo;
          int num;
          if (ModuleInfoHelper.TryReadXml(System.IO.Path.Combine(modulePath, "SubModule.xml"), out xml))
          {
            moduleInfo = ModuleInfoExtended.FromXml(xml);
            num = moduleInfo != null ? 1 : 0;
          }
          else
            num = 0;
          if (num != 0)
            yield return new ModuleInfoExtendedWithMetadata(moduleInfo, false, System.IO.Path.GetFullPath(modulePath));
          xml = (XmlDocument) null;
          moduleInfo = (ModuleInfoExtended) null;
          modulePath = (string) null;
        }
        strArray = (string[]) null;
      }
    }

    public static IEnumerable<ModuleInfoExtendedWithMetadata> GetPlatformModules()
    {
      if (ModuleInfoHelper._platformModuleExtensionField != null)
      {
        IPlatformModuleExtension platformModuleExtension = ModuleInfoHelper._platformModuleExtensionField.Invoke();
        if (platformModuleExtension != null)
        {
          string[] modulePaths = platformModuleExtension.GetModulePaths();
          if (modulePaths != null)
          {
            string[] strArray = modulePaths;
            for (int index = 0; index < strArray.Length; ++index)
            {
              string modulePath = strArray[index];
              XmlDocument xml;
              ModuleInfoExtended moduleInfo;
              int num;
              if (ModuleInfoHelper.TryReadXml(System.IO.Path.Combine(modulePath, "SubModule.xml"), out xml))
              {
                moduleInfo = ModuleInfoExtended.FromXml(xml);
                num = moduleInfo != null ? 1 : 0;
              }
              else
                num = 0;
              if (num != 0)
                yield return new ModuleInfoExtendedWithMetadata(moduleInfo, true, System.IO.Path.GetFullPath(modulePath));
              xml = (XmlDocument) null;
              moduleInfo = (ModuleInfoExtended) null;
              modulePath = (string) null;
            }
            strArray = (string[]) null;
          }
        }
      }
    }

    public static bool CheckIfSubModuleCanBeLoaded(SubModuleInfoExtended subModuleInfo)
    {
      return ModuleInfoHelper.CheckIfSubModuleCanBeLoaded(subModuleInfo, ApplicationPlatform.CurrentPlatform, ApplicationPlatform.CurrentRuntimeLibrary, TaleWorlds.MountAndBlade.Module.CurrentModule.StartupInfo.DedicatedServerType, TaleWorlds.MountAndBlade.Module.CurrentModule.StartupInfo.PlayerHostedDedicatedServer);
    }

    public static bool CheckIfSubModuleCanBeLoaded(
      SubModuleInfoExtended subModuleInfo,
      Platform cPlatform,
      Runtime cRuntime,
      DedicatedServerType cServerType,
      bool playerHostedDedicatedServer)
    {
      if (subModuleInfo.Tags.Count <= 0)
        return true;
      foreach (KeyValuePair<string, IReadOnlyList<string>> tag1 in (IEnumerable<KeyValuePair<string, IReadOnlyList<string>>>) subModuleInfo.Tags)
      {
        string key;
        IReadOnlyList<string> stringList;
        tag1.Deconstruct<string, IReadOnlyList<string>>(out key, out stringList);
        string str = key;
        IReadOnlyList<string> source = stringList;
        Bannerlord.ModuleManager.SubModuleTags tag;
        if (Enum.TryParse<Bannerlord.ModuleManager.SubModuleTags>(str, out tag) && source.Any<string>((Func<string, bool>) (value => !ModuleInfoHelper.GetSubModuleTagValiditiy(tag, value, cPlatform, cRuntime, cServerType, playerHostedDedicatedServer))))
          return false;
      }
      return true;
    }

    public static bool GetSubModuleTagValiditiy(Bannerlord.ModuleManager.SubModuleTags tag, string value)
    {
      return ModuleInfoHelper.GetSubModuleTagValiditiy(tag, value, ApplicationPlatform.CurrentPlatform, ApplicationPlatform.CurrentRuntimeLibrary, TaleWorlds.MountAndBlade.Module.CurrentModule.StartupInfo.DedicatedServerType, TaleWorlds.MountAndBlade.Module.CurrentModule.StartupInfo.PlayerHostedDedicatedServer);
    }

    public static bool GetSubModuleTagValiditiy(
      Bannerlord.ModuleManager.SubModuleTags tag,
      string value,
      Platform cPlatform,
      Runtime cRuntime,
      DedicatedServerType cServerType,
      bool playerHostedDedicatedServer)
    {
      if (true)
        ;
      bool moduleTagValiditiy;
      switch (tag)
      {
        case Bannerlord.ModuleManager.SubModuleTags.RejectedPlatform:
          Platform result1;
          moduleTagValiditiy = !Enum.TryParse<Platform>(value, out result1) || cPlatform != result1;
          break;
        case Bannerlord.ModuleManager.SubModuleTags.ExclusivePlatform:
          Platform result2;
          moduleTagValiditiy = !Enum.TryParse<Platform>(value, out result2) || cPlatform == result2;
          break;
        case Bannerlord.ModuleManager.SubModuleTags.DedicatedServerType:
          string lower = value.ToLower();
          if (true)
            ;
          bool flag;
          switch (lower)
          {
            case "none":
              flag = cServerType == DedicatedServerType.None;
              break;
            case "both":
              flag = cServerType == DedicatedServerType.None;
              break;
            case "custom":
              flag = cServerType == DedicatedServerType.Custom;
              break;
            case "matchmaker":
              flag = cServerType == DedicatedServerType.Matchmaker;
              break;
            default:
              flag = false;
              break;
          }
          if (true)
            ;
          moduleTagValiditiy = flag;
          break;
        case Bannerlord.ModuleManager.SubModuleTags.IsNoRenderModeElement:
          moduleTagValiditiy = value.Equals("false");
          break;
        case Bannerlord.ModuleManager.SubModuleTags.DependantRuntimeLibrary:
          Runtime result3;
          moduleTagValiditiy = !Enum.TryParse<Runtime>(value, out result3) || cRuntime == result3;
          break;
        case Bannerlord.ModuleManager.SubModuleTags.PlayerHostedDedicatedServer:
          moduleTagValiditiy = playerHostedDedicatedServer && value.Equals("true");
          break;
        default:
          moduleTagValiditiy = true;
          break;
      }
      if (true)
        ;
      return moduleTagValiditiy;
    }

    public static bool ValidateLoadOrder(System.Type subModuleType, out string report)
    {
      return ModuleInfoHelper.ValidateLoadOrder((ModuleInfoExtended) ModuleInfoHelper.GetModuleByType(subModuleType), out report);
    }

    public static bool ValidateLoadOrder(ModuleInfoExtended? moduleInfo, out string report)
    {
      if ((object) moduleInfo == null)
      {
        report = "CRITICAL ERROR";
        return false;
      }
      List<ModuleInfoExtendedWithMetadata> list = ModuleInfoHelper.GetLoadedModules().ToList<ModuleInfoExtendedWithMetadata>();
      int moduleIndex = IndexOf<ModuleInfoExtended>((IReadOnlyList<ModuleInfoExtended>) list, moduleInfo);
      StringBuilder sb = new StringBuilder();
      foreach (DependentModule dependentModule in (IEnumerable<DependentModule>) moduleInfo.DependentModules)
      {
        DependentModule dependedModule = dependentModule;
        ModuleInfoExtendedWithMetadata extendedWithMetadata = list.SingleOrDefault<ModuleInfoExtendedWithMetadata>((Func<ModuleInfoExtendedWithMetadata, bool>) (x => x.Id == dependedModule.Id));
        ValidateDependedModuleLoadBeforeThis((object) extendedWithMetadata != null ? list.IndexOf(extendedWithMetadata) : -1, dependedModule.Id);
      }
      foreach (DependentModuleMetadata dependentModuleMetadata in (IEnumerable<DependentModuleMetadata>) moduleInfo.DependentModuleMetadatas)
      {
        DependentModuleMetadata dependedModule = dependentModuleMetadata;
        ModuleInfoExtendedWithMetadata extendedWithMetadata = list.SingleOrDefault<ModuleInfoExtendedWithMetadata>((Func<ModuleInfoExtendedWithMetadata, bool>) (x => x.Id == dependedModule.Id));
        int num = (object) extendedWithMetadata != null ? list.IndexOf(extendedWithMetadata) : -1;
        if (dependedModule.IsIncompatible)
        {
          if (moduleInfo.DependentModules.Any<DependentModule>((Func<DependentModule, bool>) (dm => dm.Id == dependedModule.Id)))
            ReportMutuallyExclusiveDirectives(dependedModule.Id);
          else
            ValidateDependedModuleCompatibility(num, dependedModule.Id);
        }
        else if (dependedModule.LoadType == LoadType.LoadBeforeThis)
        {
          if (!moduleInfo.DependentModules.Any<DependentModule>((Func<DependentModule, bool>) (dm => dm.Id == dependedModule.Id)))
            ValidateDependedModuleLoadBeforeThis(num, dependedModule.Id, dependedModule.IsOptional);
        }
        else if (dependedModule.LoadType == LoadType.LoadAfterThis)
        {
          if (moduleInfo.DependentModules.Any<DependentModule>((Func<DependentModule, bool>) (dm => dm.Id == dependedModule.Id)) || moduleInfo.DependentModuleMetadatas.Any<DependentModuleMetadata>((Func<DependentModuleMetadata, bool>) (dm => dm.Id == dependedModule.Id && dm.LoadType == LoadType.LoadBeforeThis)))
            ReportMutuallyExclusiveDirectives(dependedModule.Id);
          else
            ValidateDependedModuleLoadAfterThis(num, dependedModule.Id, dependedModule.IsOptional);
        }
      }
      if (sb.Length > 0)
      {
        report = sb.ToString();
        return false;
      }
      report = string.Empty;
      return true;

      static int IndexOf<T>(IReadOnlyList<
      #nullable disable
      T> self, T elementToFind)
      {
        int num = 0;
        foreach (T objA in (IEnumerable<T>) self)
        {
          if (object.Equals((object) objA, (object) elementToFind))
            return num;
          ++num;
        }
        return -1;
      }

      void ReportMissingModule(
      #nullable enable
      string requiredModuleId)
      {
        if (sb.Length != 0)
          sb.AppendLine();
        sb.AppendLine(new TextObject("{=FE6ya1gzZR}{REQUIRED_MODULE} module was not found!")?.SetTextVariable("REQUIRED_MODULE", requiredModuleId)?.ToString() ?? "ERROR");
      }

      void ReportIncompatibleModule(string deniedModuleId)
      {
        if (sb.Length != 0)
          sb.AppendLine();
        sb.AppendLine(new TextObject("{=EvI6KPAqTT}Incompatible module {DENIED_MODULE} was found!")?.SetTextVariable("DENIED_MODULE", deniedModuleId)?.ToString() ?? "ERROR");
      }

      void ReportLoadingOrderIssue(string reason, string requiredModuleId)
      {
        if (sb.Length != 0)
          sb.AppendLine();
        sb.AppendLine(new TextObject(reason)?.SetTextVariable("MODULE", moduleInfo.Id)?.SetTextVariable("REQUIRED_MODULE", requiredModuleId)?.SetTextVariable("NL", Environment.NewLine)?.ToString() ?? "ERROR");
      }

      void ReportMutuallyExclusiveDirectives(string requiredModuleId)
      {
        if (sb.Length != 0)
          sb.AppendLine();
        sb.AppendLine(new TextObject("{=FcR4BXnhx8}{MODULE} has mutually exclusive mod order directives specified for the {REQUIRED_MODULE}!")?.SetTextVariable("MODULE", moduleInfo.Id)?.SetTextVariable("REQUIRED_MODULE", requiredModuleId)?.ToString() ?? "ERROR");
      }

      void ValidateDependedModuleCompatibility(int deniedModuleIndex, string deniedModuleId)
      {
        if (deniedModuleIndex == -1)
          return;
        ReportIncompatibleModule(deniedModuleId);
      }

      void ValidateDependedModuleLoadBeforeThis(
        int requiredModuleIndex,
        string requiredModuleId,
        bool isOptional = false)
      {
        if (!isOptional && requiredModuleIndex == -1)
        {
          ReportMissingModule(requiredModuleId);
        }
        else
        {
          if (requiredModuleIndex <= moduleIndex)
            return;
          ReportLoadingOrderIssue("{=5G9zffrgMh}{MODULE} is loaded before the {REQUIRED_MODULE}!{NL}Make sure {MODULE} is loaded after it!", requiredModuleId);
        }
      }

      void ValidateDependedModuleLoadAfterThis(
        int requiredModuleIndex,
        string requiredModuleId,
        bool isOptional)
      {
        if (requiredModuleIndex == -1)
        {
          if (isOptional)
            return;
          ReportMissingModule(requiredModuleId);
        }
        else
        {
          if (requiredModuleIndex >= moduleIndex)
            return;
          ReportLoadingOrderIssue("{=UZ8zfvudMs}{MODULE} is loaded after the {REQUIRED_MODULE}!{NL}Make sure {MODULE} is loaded before it!", requiredModuleId);
        }
      }
    }

    public static bool IsModuleAssembly(
      ModuleInfoExtendedWithMetadata loadedModule,
      Assembly assembly)
    {
      return !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.CodeBase) && ModuleInfoHelper.IsInModule(loadedModule, assembly.CodeBase);
    }

    public static bool IsInModule(ModuleInfoExtendedWithMetadata loadedModule, string filePath)
    {
      if (string.IsNullOrWhiteSpace(filePath))
        return false;
      Uri uri1 = new Uri(System.IO.Path.GetFullPath(loadedModule.Path));
      string fileName = System.IO.Path.GetFileName(loadedModule.Path);
      Uri uri2 = new Uri(filePath);
      return uri1.MakeRelativeUri(uri2).OriginalString.StartsWith(fileName);
    }

    private static bool TryReadXml(string path, out XmlDocument? xml)
    {
      try
      {
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.LoadXml(File.ReadAllText(path));
        xml = xmlDocument;
        return true;
      }
      catch (Exception ex)
      {
        xml = (XmlDocument) null;
        return false;
      }
    }
  }
}
